// i.r(t), i.d(t, { default: () => RaycasterModule })

import * as THREE from "three"
import { ScheduleProcessCommand } from "../command/task.command"
import CameraData from "../data/CameraData"
import RaycasterData, { RaycastHit } from "../data/RaycasterData"
import EngineContext from "../EngineContext"
import a from "../math/19674"
import T from "../math/27687"
import p from "../math/7170"
import l from "../math/74384"
import SymbolList from "../SymbolList"
import LineOctree from "../three/LineOctree"
import SnapLine3 from "../three/SnapLine3"
import SnapVector3 from "../three/SnapVector3"
import funcUtil from "../utils/func.util"
import Logger from "../utils/Logger"
import { BufferGeometryExt } from "../utils/MeshUtil"
import Module from "./Module"
import WebGLRendererModule from "./WebGLRendererModule"
class ColliderNode {
  parent: ColliderOctreeNode
  raycastDistance: number
  distance: number
  bounds: THREE.Box3
  constructor() {
    this.parent = null
    this.raycastDistance = 1 / 0
    this.distance = 1 / 0
  }
  updateRaycastDistance(e: THREE.Vector3) {
    this.raycastDistance = this.bounds.distanceToPoint(e)
  }
  updateDistanceToBounds(e: THREE.Vector3) {
    this.distance = this.bounds.distanceToPoint(e)
  }
}
class ColliderMeshTarget extends ColliderNode {
  mesh: THREE.Mesh<BufferGeometryExt>
  center: THREE.Vector3
  constructor(e) {
    super()
    this.mesh = e
    this.bounds = l.A5(this.mesh.geometry).clone().applyMatrix4(this.mesh.matrixWorld)
    this.center = this.bounds.getCenter(new THREE.Vector3())
  }
  raycast(e, t) {
    this.mesh.visible && this.mesh.raycast(e, t)
  }
  nearestPointOnGeometry(e: THREE.Vector3) {
    const t = e.clone().applyMatrix4(new THREE.Matrix4().copy(this.mesh.matrixWorld).invert())
    let i: THREE.Intersection
    const n = this.mesh.geometry.boundsTree
    i = n ? n.closestPointToPoint(t) : l.GO(this.mesh.geometry, t)
    i.point.applyMatrix4(this.mesh.matrixWorld)
    return {
      object: this.mesh,
      point: i.point,
      distance: i.point.distanceTo(e),
      faceIndex: i.faceIndex
    }
  }
}
class ColliderOctreeNode extends ColliderNode {
  level: number
  leafMinSize: number
  childNodes: ColliderOctreeNode[]
  children: (ColliderOctreeNode | ColliderMeshTarget)[]
  listeners: {}
  size: THREE.Vector3
  center: THREE.Vector3
  constructor(bounds: THREE.Box3, level: number, leafMinSize: number) {
    super()
    this.level = level
    this.leafMinSize = leafMinSize
    this.childNodes = []
    this.children = []
    this.listeners = {}
    this.bounds = bounds.clone()
    this.size = bounds.getSize(new THREE.Vector3())
    this.center = bounds.getCenter(new THREE.Vector3())
  }
  get depth() {
    return this.level
  }
  *addMeshes(e) {
    e.updateMatrixWorld(!0)
    const t: THREE.Mesh[] = []
    e.traverse(e => {
      e instanceof THREE.Mesh && e.geometry && t.push(e)
    })
    for (const e of t) this.addMesh(new ColliderMeshTarget(e))
  }
  removeMeshes(e) {
    let t = !1
    for (let i = 0; i < this.children.length; i++) {
      const n = this.children[i]
      n instanceof ColliderMeshTarget && n.mesh.id === e.id && ((n.parent = null), this.children.splice(i, 1), this.emit("removed", this, n), (t = !0))
    }
    if (!t) throw new Error("Mesh that is being attempted removed does not exist in this node.")
  }
  addMesh(e: ColliderMeshTarget) {
    let t = !1
    if (this.size.length() > this.leafMinSize) {
      0 === this.childNodes.length && this.buildChildNodes()
      for (const i of this.childNodes) i.bounds.containsBox(e.bounds) && ((t = !0), i.addMesh(e))
    }
    t || this.add(e)
  }
  add(e: ColliderMeshTarget | ColliderOctreeNode) {
    if (e.parent) throw new Error("ColliderNode already has a parent, remove before trying to add in another node")
    e.parent = this
    this.children.push(e)
    this.emit("added", this, e)
  }
  buildChildNodes() {
    const e = this.size.multiplyScalar(0.5)
    for (let t = 0; t < 2; t++)
      for (let i = 0; i < 2; i++)
        for (let n = 0; n < 2; n++) {
          const s = this.bounds.min.x + e.x * n,
            r = s + e.x,
            a = this.bounds.min.y + e.y * t,
            l = a + e.y,
            c = this.bounds.min.z + e.z * i,
            h = c + e.z,
            d = new THREE.Box3(new THREE.Vector3(s, a, c), new THREE.Vector3(r, l, h)),
            u = new ColliderOctreeNode(d, this.level + 1, this.leafMinSize)
          this.childNodes.push(u)
          this.add(u)
        }
  }
  raycast(e, t) {
    if (!0 === e.ray.intersectsBox(this.bounds)) for (const i of this.children) i.raycast(e, t)
  }
  pick(e: THREE.Raycaster, t, i) {
    if (!e.ray.intersectsBox(this.bounds)) return t
    for (const t of this.children) t.updateRaycastDistance(e.ray.origin)
    this.children.sort((e, t) => e.raycastDistance - t.raycastDistance)
    for (const n of this.children) {
      if (t && t.distance < n.raycastDistance) break
      if (i(n))
        if (n instanceof ColliderOctreeNode) t = n.pick(e, t, i)
        else {
          const i = []
          n.raycast(e, i)
          for (const e of i) (!t || t.distance > e.distance) && (t = e)
        }
    }
    return t
  }
  nearestTo(e: THREE.Vector3, t: (e: ColliderOctreeNode | ColliderMeshTarget) => boolean, i: THREE.Intersection = null) {
    for (const t of this.children) t.updateDistanceToBounds(e)
    this.children.sort((e, t) => e.distance - t.distance)
    for (const n of this.children) {
      if (0 !== n.distance && i && i.distance < n.distance) break
      if (t(n))
        if (n instanceof ColliderOctreeNode) i = n.nearestTo(e, t, i)
        else if (n instanceof ColliderMeshTarget) {
          const t = n.nearestPointOnGeometry(e)
          if (!i || i.distance > t.distance) {
            i = t
          }
        }
    }
    return i
  }
  getAllBounds(e, t = 0, i = 1 / 0, n = !1) {
    for (const s of this.children)
      this.level >= t && this.level <= i && (n || e.push(s.bounds.clone())), s instanceof ColliderOctreeNode && s.getAllBounds(e, t, i, n)
  }
  emit(e, ...t) {
    const i = this.listeners[e] || []
    for (const e of i) e(...t)
    this.parent && this.parent.emit(e, ...t)
  }
  on(e, t) {
    this.listeners[e] || (this.listeners[e] = []), this.listeners[e].push(t)
  }
}
class OctreeCollider {
  meshToNodes: Record<number, ColliderMeshTarget[]>
  _depth: number
  root: ColliderOctreeNode
  constructor(e: THREE.Box3, leafMinSize: number) {
    this.meshToNodes = {}
    this._depth = 0
    const bounds = a.rj(e)
    this.root = new ColliderOctreeNode(bounds, 0, leafMinSize)
    this.root.on("added", (e, t) => {
      e.depth > this._depth && (this._depth = e.depth),
        t instanceof ColliderMeshTarget && (this.meshToNodes[t.mesh.id] || (this.meshToNodes[t.mesh.id] = []), this.meshToNodes[t.mesh.id].push(t))
    })
    this.root.on("removed", (e, t) => {
      if (t instanceof ColliderMeshTarget) {
        const e = this.meshToNodes[t.mesh.id]
        for (let i = 0; i < e.length; i++) e[i] === t && e.splice(i, 1)
      }
    })
  }
  get depth() {
    return this._depth
  }
  *addMeshesAsync(e) {
    yield* this.root.addMeshes(e)
  }
  addMeshes(e) {
    for (const t of this.root.addMeshes(e));
  }
  removeMeshes(e) {
    e.traverse(e => {
      const t = this.meshToNodes[e.id] || []
      for (const i of t) i.parent && i.parent.removeMeshes(e)
    })
  }
  raycast(e) {
    const t = []
    return this.root.raycast(e, t), t
  }
  getAllBounds(e = 0, t = 1 / 0, i = !1) {
    const n = []
    return this.root.getAllBounds(n, e, t, i), n
  }
  pick(e: THREE.Raycaster, t: (e?: any) => boolean) {
    const i = e["firstHitOnly"]
    e["firstHitOnly"] = !0
    const n = this.root.pick(e, null, e => !(t && e instanceof ColliderMeshTarget) || t(e.mesh))
    e["firstHitOnly"] = i
    return n
  }
  nearestMeshTo(e: THREE.Vector3, t?: (e: THREE.Mesh) => boolean) {
    const i = this.root.bounds.clampPoint(e, new THREE.Vector3())
    return this.root.nearestTo(i, e => !(t && e instanceof ColliderMeshTarget) || t(e.mesh))
  }
  getDebugBoundsMesh(e = 16777215, t = 0, i = 1 / 0, n = !1) {
    const s = this.getAllBounds(t, i, n),
      r = new THREE.LineBasicMaterial({ color: e })
    if (s.length > 0) {
      const e = []
      for (const t of s) {
        const i = new THREE.Box3Helper(t)
        e.push(i.geometry)
      }
      const t = l.qf(e)
      return new THREE.LineSegments(t, r)
    }
    return new THREE.LineSegments(new THREE.BufferGeometry(), r)
  }
}
class Raycaster {
  camera: THREE.PerspectiveCamera
  defaultRaycastFilter: <T extends THREE.Mesh>(e: T) => boolean
  meshes: Record<number, THREE.Mesh>
  octreeEnabledMeshes: Record<string, boolean>
  meshFilters: Map<THREE.Mesh, (e?: any) => boolean>
  raycaster: THREE.Raycaster
  prevRay: THREE.Ray
  _hitCache: THREE.Intersection[]
  getOctree: () => OctreeCollider
  octree: OctreeCollider
  constructor(e: THREE.PerspectiveCamera, t) {
    this.camera = e
    this.defaultRaycastFilter = t
    this.meshes = {}
    this.octreeEnabledMeshes = {}
    this.meshFilters = new Map()
    this.raycaster = new THREE.Raycaster()
    this.prevRay = new THREE.Ray()
    this._hitCache = []
    this.getOctree = () => this.octree
  }
  init() {
    this.raycaster = new THREE.Raycaster()
    this.raycaster.camera = this.camera
    this.raycaster.params.Line && (this.raycaster.params.Line.threshold = 0.1)
  }
  dispose() {
    this._hitCache.length = 0
    this.meshes = {}
  }
  cast(e?, t?, i?) {
    e && t && (this.prevRay.copy(this.raycaster.ray), this.raycaster.set(e, t))
    if (this.prevRay.equals(this.raycaster.ray)) return this._hitCache
    if (((this.raycaster.layers.mask = this.camera.layers.mask), (this._hitCache.length = 0), this.octree)) {
      const e = this.getRaycastFilter(c.OctreeMeshes, i),
        t = this.octree.pick(this.raycaster, e)
      t && this._hitCache.push(t)
    }
    const n = this.getRaycastFilter(c.DynamicMeshes, i)
    for (const e in this.meshes) {
      const t = this.meshes[e]
      if (n(t)) {
        const e = this.raycaster.intersectObject(t, !0)
        this._hitCache = this._hitCache.concat(e)
      }
    }
    this._hitCache.sort(this.sortIntersectionsByDistance)
    this.prevRay.copy(this.raycaster.ray)
    return this._hitCache
  }
  pick(e: THREE.Vector3, t: THREE.Vector3, i: (e?: any) => boolean) {
    this.raycaster.set(e, t)
    const n = this.getRaycastFilter(c.OctreeMeshes, i)
    return this.octree.pick(this.raycaster, n)
  }
  nearest(e: THREE.Vector3, t: Function) {
    return this.octree.nearestMeshTo(e, this.getRaycastFilter(c.OctreeMeshes, t))
  }
  setupOctree(e: THREE.Box3) {
    this.octree = new OctreeCollider(e, 0.5)
    for (const e of Object.keys(this.meshes))
      if (this.octreeEnabledMeshes[e]) {
        const t = this.meshes[e]
        this.octree.addMeshes(t)
      }
    return this.octree
  }
  addTarget<T extends THREE.Mesh>(e: T, needOctree: boolean, meshFilters?: (e?: any) => boolean) {
    const n = e
    const s = n["collider"] ? n["collider"] : e
    this.meshes[e.id] = s
    meshFilters && this.meshFilters.set(s, meshFilters)
    this.octreeEnabledMeshes[e.id] = needOctree
    if (this.octree && needOctree) {
      this.octree.addMeshes(e)
      e.traverse(e => {
        if (e instanceof THREE.Mesh) {
          this.octreeEnabledMeshes[e.id] = needOctree
          meshFilters && this.meshFilters.set(e, meshFilters)
        }
      })
    }
  }
  removeTarget(e: THREE.Mesh) {
    this.octree &&
      this.octreeEnabledMeshes[e.id] &&
      (this.octree.removeMeshes(e),
      (this.octreeEnabledMeshes[e.id] = !1),
      e.traverse(e => {
        e instanceof THREE.Mesh && ((this.octreeEnabledMeshes[e.id] = !1), this.meshFilters.delete(e))
      }))
    const t = this.meshes[e.id]
    t && (this.meshFilters.delete(t), delete this.meshes[e.id])
  }
  getRaycastFilter<T extends THREE.Mesh>(e: number, t: Function): (e: T) => boolean {
    const i = t || this.defaultRaycastFilter
    const n = (t: T) => {
      const i = this.octreeEnabledMeshes[t.id]
      switch (e) {
        case c.DynamicMeshes:
          return !i
        case c.OctreeMeshes:
          return i
      }
    }
    return e => n(e) && i(e) && this.checkMeshSpecificFilter(e)
  }
  checkMeshSpecificFilter<T extends THREE.Mesh>(e: T) {
    const t = this.meshFilters.get(e)
    return !t || t(e)
  }
  sortIntersectionsByDistance(e, t) {
    return e.distance > t.distance ? 1 : e.distance < t.distance ? -1 : 0
  }
  render() {}
  activate() {}
  deactivate() {}
}
var c = {
  0: "OctreeMeshes",
  1: "DynamicMeshes",
  DynamicMeshes: 1,
  OctreeMeshes: 0
}

class LegacyThreeGeometry {
  vertices: THREE.Vector3[]
  colors: THREE.Color[]
  faces: Face3[]
  faceVertexUvs: Array<Array<THREE.Vector2>>
  boundingBox: THREE.Box3
  boundingSphere: THREE.Sphere
  constructor() {
    this.vertices = []
    this.colors = []
    this.faces = []
    this.faceVertexUvs = [[], []]
    this.boundingBox = null
    this.boundingSphere = null
  }
}
class Face3 {
  a: number
  b: number
  c: number
  materialIndex: number
  normal: THREE.Vector3
  vertexNormals: THREE.Vector3[]
  color: THREE.Color
  vertexColors: THREE.Color[]
  constructor(e, t, i, n, s?, r = 0) {
    this.a = e
    this.b = t
    this.c = i
    this.materialIndex = r
    this.a = e
    this.b = t
    this.c = i
    this.normal = n && !Array.isArray(n) && n.isVector3 ? n : new THREE.Vector3()
    this.vertexNormals = Array.isArray(n) ? n : []
    this.color = s && !Array.isArray(s) && s.isColor ? s : new THREE.Color()
    this.vertexColors = Array.isArray(s) ? s : []
    this.materialIndex = r
  }
  clone() {
    return new Face3(this.a, this.b, this.c, this.normal, this.color).copy(this)
  }
  copy(e: Face3) {
    this.a = e.a
    this.b = e.b
    this.c = e.c
    this.normal.copy(e.normal)
    this.color.copy(e.color)
    this.materialIndex = e.materialIndex
    for (let t = 0, i = e.vertexNormals.length; t < i; t++) this.vertexNormals[t] = e.vertexNormals[t].clone()
    for (let t = 0, i = e.vertexColors.length; t < i; t++) this.vertexColors[t] = e.vertexColors[t].clone()
    return this
  }
}

const m = new Logger("surface-edge-finder"),
  g = new THREE.Triangle(),
  f = ["a", "b", "c"]
class Face extends Face3 {
  va: Vertex
  vb: Vertex
  vc: Vertex
  area: number
  midpoint: THREE.Vector3
  vertices: Vertex[]
  surface: Surface
  constructor(e: Face3, t: Vertex, i: Vertex, n: Vertex) {
    super(e.a, e.b, e.c, e.normal)
    this.va = t
    this.vb = i
    this.vc = n
    this.area = g.set(this.va.vector, this.vb.vector, this.vc.vector).getArea()
    this.midpoint = g.getMidpoint(new THREE.Vector3())
    this.vertices = [this.va, this.vb, this.vc]
    t.faces.push(this)
    i.faces.push(this)
    n.faces.push(this)
  }
}
class Vertex {
  vector: THREE.Vector3
  faces: Face[]
  constructor(e: THREE.Vector3) {
    this.vector = e
    this.faces = []
  }
}
class Surface {
  area: number
  normal: THREE.Vector3
  midpoint: THREE.Vector3
  faces: Face[]
  normalSum: THREE.Vector3
  midpointSum: THREE.Vector3
  constructor() {
    this.area = 0
    this.normal = new THREE.Vector3()
    this.midpoint = new THREE.Vector3()
    this.faces = []
    this.normalSum = new THREE.Vector3()
    this.midpointSum = new THREE.Vector3()
  }
  add(e: Face) {
    e.surface && e.surface.remove(e)
    this.faces.push(e)
    this.area += e.area
    this.midpointSum.add(e.midpoint)
    this.normalSum.add(e.normal)
    this.recalcFromSums()
    e.surface = this
  }
  remove(e) {
    if (e.surface) {
      this.faces.splice(this.faces.indexOf(e), 1)
      this.area -= e.area
      this.midpointSum.sub(e.midpoint)
      this.normalSum.sub(e.normal)
      this.recalcFromSums()
    }
    e.surface = null
  }
  recalcFromSums() {
    this.normal.copy(this.normalSum).normalize()
    this.midpoint.copy(this.midpointSum).divideScalar(this.faces.length)
  }
  mergeSurfaces(e) {
    for (const t of e.faces) {
      t.surface = this
      this.faces.push(t)
    }
    this.area += e.area
    this.normalSum.add(e.normalSum)
    this.midpointSum.add(e.midpointSum)
    this.recalcFromSums()
    e.faces = []
    e.area = 0
    e.normal.set(0, 0, 0)
    e.normalSum.set(0, 0, 0)
    e.midpointSum.set(0, 0, 0)
    e.midpoint.set(0, 0, 0)
  }
  getEdges() {
    const e: Record<string, any> = {}
    const t = {}
    for (const t of this.faces)
      for (let i = 0; i < 3; i++) {
        const n = t[f[i]],
          s = t[f[(i + 1) % 3]],
          r = Math.min(n, s) + "," + Math.max(n, s)
        if (void 0 === e[r]) {
          const t = { edge1: n, edge2: s, isEdge: !0 }
          e[r] = t
        } else e[r].isEdge = !1
      }
    for (const { edge1: i, edge2: n, isEdge: s } of Object.values(e)) {
      if (s) {
        t[i] = t[i] || []
        t[i].push(n)
        t[n] = t[n] || []
        t[n].push(i)
      }
    }
    return t
  }
  getCircularPaths(e: Vertex[]) {
    const t = this.getEdges()
    const i = new Set()
    const n: Vertex[][] = []
    const s = Object.keys(t).map(e => parseInt(e, 10))
    let r
    for (; (r = s.pop()); ) {
      const s: Vertex[] = []
      for (; r; ) {
        let n
        s.push(e[r])
        let o = 0
        for (const s of t[r]) {
          const t = Math.min(r, s) + "," + Math.max(r, s)
          if (i.has(t)) continue
          const a = e[r].vector.distanceToSquared(this.midpoint)
          ;(void 0 === n || a > o) && ((n = s), (o = a))
        }
        if (!n) break
        {
          const e = Math.min(r, n) + "," + Math.max(r, n)
          i.add(e), (r = n)
        }
      }
      s.length > 2 && n.push(s)
    }
    return n
  }
}
class SurfaceEdgeFinder {
  surfaces: Set<Surface>
  lines: SnapLine3[]
  points: SnapVector3[]
  constructor() {
    this.surfaces = new Set()
    this.lines = []
    this.points = []
  }
  *run(e, t = [1, 5, 10], i = 0.2, n = 0.01, s = 20) {
    const r = (function (e) {
        const t = new LegacyThreeGeometry()
        const i = null !== e.index ? e.index : void 0
        const n = e.attributes
        if (void 0 === n.position) throw Error("THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.")
        const s = n.position
        const r = n.normal
        const a = n.color
        const l = n.uv
        const c = n.uv2
        void 0 !== c && (t.faceVertexUvs[1] = [])
        for (let e = 0; e < s.count; e++) {
          t.vertices.push(new THREE.Vector3().fromBufferAttribute(s, e))
          void 0 !== a && t.colors.push(new THREE.Color().fromBufferAttribute(a, e))
        }

        function h(e: number, i: number, n: number, s = 0) {
          const h = void 0 === a ? [] : [t.colors[e].clone(), t.colors[i].clone(), t.colors[n].clone()]
          const d =
            void 0 === r
              ? []
              : [new THREE.Vector3().fromBufferAttribute(r, e), new THREE.Vector3().fromBufferAttribute(r, i), new THREE.Vector3().fromBufferAttribute(r, n)]
          const u = new Face3(e, i, n, d, h, s)
          t.faces.push(u),
            void 0 !== l &&
              t.faceVertexUvs[0].push(
                new THREE.Vector2().fromBufferAttribute(l, e),
                new THREE.Vector2().fromBufferAttribute(l, i),
                new THREE.Vector2().fromBufferAttribute(l, n)
              ),
            void 0 !== c &&
              t.faceVertexUvs[1].push(
                new THREE.Vector2().fromBufferAttribute(c, e),
                new THREE.Vector2().fromBufferAttribute(c, i),
                new THREE.Vector2().fromBufferAttribute(c, n)
              )
        }
        const d = e.groups
        if (d.length > 0)
          for (let e = 0; e < d.length; e++) {
            const t = d[e]
            const n = t.start
            for (let e = n, s = n + t.count; e < s; e += 3)
              void 0 !== i ? h(i.getX(e), i.getX(e + 1), i.getX(e + 2), t.materialIndex) : h(e, e + 1, e + 2, t.materialIndex)
          }
        else if (void 0 !== i) for (let e = 0; e < i.count; e += 3) h(i.getX(e), i.getX(e + 1), i.getX(e + 2))
        else for (let e = 0; e < s.count; e += 3) h(e, e + 1, e + 2)
        return t
      })(e),
      a = r.vertices.map(e => new Vertex(e)),
      l = []
    for (const e of r.faces) l.push(new Face(e, a[e.a], a[e.b], a[e.c])), yield
    for (let e = 0, t = l.length; e < t; e++) {
      const t = l[e]
      new Surface().add(t)
    }
    for (const e of t) yield* this.mergeSurfacesByNormal(l, e)
    for (const e of l) this.surfaces.add(e.surface)
    let c = 0
    const h: Vertex[][] = []
    for (const e of this.surfaces) {
      if (e.area <= i) continue
      const t = e.getCircularPaths(a)
      for (const e of t)
        this.cullPath(e, (e, t) => {
          if (e.length() < n || t.length() < n) return c++, !1
          const i = e.angleTo(t) * p.MN
          return !(i < s || i > 180 - s) || (c++, !1)
        }),
          e.length > 2 && h.push(e)
      yield
    }
    m.debug(`Culled ${c} path points`)
    const u = new Set()
    const g = new Set()
    const f = e => `${e.x.toFixed(3)},${e.y.toFixed(3)},${e.z.toFixed(3)}`
    const v = e => `${f(e.start)}:${f(e.end)}`
    const y = e => `${f(e.end)}:${f(e.start)}`
    for (const e of h)
      for (let t = 0; t < e.length; t++) {
        const i = e[t].vector,
          n = e[(t + 1) % e.length].vector,
          s = new SnapLine3(i, n),
          r = v(s),
          o = y(s),
          a = f(i)
        g.has(r) || g.has(o) || (g.add(r), g.add(o), this.lines.push(s)), u.has(a) || (u.add(a), this.points.push(new SnapVector3(i)))
      }
    yield
  }
  cullPath(e, t) {
    const i = new THREE.Vector3(),
      n = new THREE.Vector3()
    let s = !0
    for (; s; ) {
      s = !1
      for (let r = 0; r < e.length && e.length > 2; r++) {
        const o = e[0 === r ? e.length - 1 : r - 1].vector,
          a = e[r].vector,
          l = e[(r + 1) % e.length].vector
        i.copy(o).sub(a), n.copy(l).sub(a), t(i, n) || (e.splice(r, 1), (s = !0), r--)
      }
    }
  }
  *mergeSurfacesByNormal(e, t, i = 5) {
    let n = -1,
      s = 0
    for (; 0 !== n && s++ < i; ) {
      m.debug(`Merging surfaces by normal angle threshold: ${t}`)
      const i = Math.cos(p.Ue * t)
      n = 0
      let s = 0
      for (const t of e) {
        s++ % 1e3 == 0 && (yield)
        for (const e of t.vertices)
          for (const s of e.faces) {
            const e = s.surface
            t.surface !== e &&
              t.surface.normal.dot(e.normal) >= i &&
              (t.surface.faces.length > e.faces.length ? t.surface.mergeSurfaces(e) : e.mergeSurfaces(t.surface), n++)
          }
      }
      n && m.debug(`Merged ${n} surfaces by normal...`)
    }
  }
}

const S = new Logger("snapcaster")
var E = {
  0: "UNINITIALIZED",
  1: "WORKING",
  2: "READY",
  READY: 2,
  UNINITIALIZED: 0,
  WORKING: 1
}

class Snapcaster {
  cameraData: CameraData
  raycastPicker: Raycaster
  meshes: Map<any, any>
  edgeFinders: SurfaceEdgeFinder[]
  raycaster: THREE.Raycaster
  cast: (e: any, t: any, i: any, n: any) => any
  pick: (e: any, t: any, i: any, n?: any, s?: any) => { isLineOctreeIntersection: boolean; distance: any; distanceToRay: any; point: any; object: any }
  add: (...e: Array<SnapLine3 | SnapVector3>) => void
  remove: (...e: Array<SnapLine3 | SnapVector3>) => void
  engine: EngineContext
  _snappingOctree: LineOctree
  populateProcess: any
  constructor(e, t) {
    this.cameraData = e
    this.raycastPicker = t
    this.meshes = new Map()
    this.edgeFinders = []
    this.raycaster = new THREE.Raycaster()
    this.cast = (e, t, i, n) => {
      let s
      i && n && (s = new THREE.Plane().setFromNormalAndCoplanarPoint(n, i)), this.raycaster.set(e.origin, e.direction)
      const r = this.snappingOctree.raycast(this.raycaster, t) || [],
        a = e => {
          let t = (1 + e.distance) * (1 + e.distanceToRay) ** 2
          return (e.object instanceof SnapVector3 || e.object instanceof THREE.Vector3) && (t /= 10), s && s.distanceToPoint(e.point) < -0.2 && (t += 10), t
        }
      return r.sort((e, t) => a(e) - a(t)), r
    }
    this.pick = (e, t, i, n, s) => {
      i || (i = this.cast(e, t, n, s))
      let r = null
      const a = new THREE.Vector3()
      for (const t of i) {
        a.copy(t.point).sub(e.origin).normalize()
        const i = this.raycastPicker.pick(e.origin, a, funcUtil.raycastVisible)
        if (!i || i.distance > t.distance - 0.05) {
          r = t
          break
        }
      }
      return r
        ? {
            isLineOctreeIntersection: !0,
            distance: r.distance,
            distanceToRay: r.distanceToRay,
            point: r.point,
            object: r.object
          }
        : null
    }
    this.add = (...e) => {
      for (const t of e) this.snappingOctree.add(t)
    }
    this.remove = (...e) => {
      for (const t of e) this.snappingOctree.remove(t)
    }
  }
  render() {}
  init() {}
  dispose() {}
  deactivate(e) {}
  async activate(e) {
    this.engine = e
  }
  get snappingOctree() {
    this.preloadMeshSnapping()
    return this._snappingOctree
  }
  setupOctree(e: THREE.Box3) {
    this._snappingOctree = new LineOctree(e)
  }
  async preloadMeshSnapping() {
    if (!this.populateProcess && this.hasNewMeshes()) {
      this.populateProcess = await this.engine.commandBinder.issueCommand(new ScheduleProcessCommand("snapping", this.buildSnappingForMeshes(), 60000))
      if (this.populateProcess?.promise) {
        await this.populateProcess.promise
      }
      this.populateProcess = null
    }
  }
  *buildSnappingForMeshes() {
    const e = new THREE.Vector3()
    for (;;) {
      let t = null
      for (const [i, n] of this.meshes)
        if (n.status === E.UNINITIALIZED) {
          const s = l.A5(i.geometry).getCenter(e).distanceTo(this.cameraData.pose.position)
          ;(!t || s < t.distance) && (t = { mesh: i, distance: s, info: n })
        }
      if (!t) break
      t.info.status = E.WORKING
      yield* this.buildSnappingForGeometry(t.mesh.geometry)
      t.info.status = E.READY
      yield
    }
    S.info(`Snapping found ${this._snappingOctree.pointCount} points and ${this._snappingOctree.lineCount} lines`)
  }
  *buildSnappingForGeometry(e) {
    e.computeVertexNormals()
    yield
    const t = new SurfaceEdgeFinder()
    yield* t.run(e), S.debug(`Mesh done, added ${t.points.length} points and ${t.lines.length} lines`)
    for (const e of t.points) this._snappingOctree.add(e)
    for (const e of t.lines) this._snappingOctree.add(e)
    this.edgeFinders.push(t)
  }
  addMesh(e) {
    this.meshes.has(e) || this.meshes.set(e, { mesh: e, status: E.UNINITIALIZED })
  }
  hasNewMeshes() {
    return [...this.meshes.values()].some(e => e.status === E.UNINITIALIZED)
  }
}

class RaycastPointerScreen {
  raycaster: Raycaster
  cameraData: CameraData
  lastHits: THREE.Intersection[]
  lastRay: THREE.Ray
  currentRay: THREE.Ray
  pointer: THREE.Vector2
  origin: THREE.Vector3
  direction: THREE.Vector3
  cast: () => THREE.Intersection[]
  updatePointer: (e: THREE.Vector2) => void
  constructor(e, t) {
    this.raycaster = e
    this.cameraData = t
    this.lastHits = []
    this.lastRay = new THREE.Ray(new THREE.Vector3(1, 1, 1), new THREE.Vector3(0, 1, 0))
    this.currentRay = new THREE.Ray()
    this.pointer = new THREE.Vector2(-2, -2)
    this.origin = new THREE.Vector3()
    this.direction = new THREE.Vector3()
    this.cast = () => {
      const e = this.pointerRay
      this.lastRay.equals(e) || ((this.lastHits = this.raycaster.cast(e.origin, e.direction).slice()), this.lastRay.copy(e))
      return this.lastHits
    }
    this.updatePointer = e => {
      this.pointer.distanceToSquared(e) > 2e-5 && this.pointer.set(e.x, e.y)
    }
  }
  get pointerRay() {
    const { origin: e, direction: t } = this.updateDirections()
    return this.currentRay.set(e, t)
  }
  get ndcPosition() {
    return this.pointer
  }
  updateDirections() {
    T.Kh(this.cameraData.pose, this.origin.set(this.pointer.x, this.pointer.y, -1), this.origin)
    T.Kh(this.cameraData.pose, this.direction.set(this.pointer.x, this.pointer.y, 1), this.direction)
    this.direction.sub(this.origin).normalize()
    return { origin: this.origin, direction: this.direction }
  }
}
class RaycastPointerDataUpdate {
  raycaster: RaycasterModule
  data: RaycasterData
  cameraData: CameraData
  cache: { normal4: THREE.Vector4; normal: THREE.Vector3; rotationMatrix: THREE.Matrix4; ndc3: THREE.Vector3 }
  constructor(e, t, i) {
    this.raycaster = e
    this.data = t
    this.cameraData = i
    this.cache = {
      normal4: new THREE.Vector4(),
      normal: new THREE.Vector3(),
      rotationMatrix: new THREE.Matrix4(),
      ndc3: new THREE.Vector3()
    }
  }
  beforeRender() {
    if (this.raycaster.pointer.pointerRay.equals(this.data.pointerRay)) return
    this.data.pointerRay.copy(this.raycaster.pointer.pointerRay)
    this.data.pointerOrigin.copy(this.data.pointerRay.origin)
    this.data.pointerDirection.copy(this.data.pointerRay.direction)
    const e = this.raycaster.pointer.cast()
    this.data.hits.length = 0
    this.data.hits.push(...e)
    this.data.pointerNdcPosition.copy(this.raycaster.pointer.ndcPosition)
    this.cache.ndc3.set(this.data.pointerNdcPosition.x, this.data.pointerNdcPosition.y, 0.5)
    this.data.pointerScreenPosition.copy(T.fi(this.cameraData.width, this.cameraData.height, this.cache.ndc3))
    if (e.length > 0) {
      const t = e[0]
      if (t && t.face) {
        const e = t.face.normal
        this.cache.normal4.set(e.x, e.y, e.z, 0)
        this.cache.rotationMatrix.extractRotation(t.object.matrixWorld)
        this.cache.normal4.applyMatrix4(this.cache.rotationMatrix)
        this.cache.normal.set(this.cache.normal4.x, this.cache.normal4.y, this.cache.normal4.z)
        this.data.hit = new RaycastHit(t.point.clone(), this.cache.normal.clone(), t.object, t)
      }
    } else this.data.hit = null
    this.data.commit()
  }
  init() {}
  render() {}
  dispose() {}
  activate() {}
  deactivate() {}
}
export default class RaycasterModule extends Module {
  raycasterPointerData: RaycasterData
  overridePointer: RaycastPointerScreen
  setOverridePointer: (e: RaycastPointerScreen) => void
  raycaster: Raycaster
  defaultPointer: RaycastPointerScreen
  snapcaster: Snapcaster
  constructor() {
    super(...arguments)
    this.name = "raycaster"
    this.raycasterPointerData = new RaycasterData()
    this.overridePointer = null
    this.setOverridePointer = e => {
      this.overridePointer = e
    }
  }
  async init(e, t: EngineContext) {
    t.market.register(this, RaycasterData, this.raycasterPointerData)
    const [i, n] = await Promise.all([t.getModuleBySymbol<WebGLRendererModule>(SymbolList.WEBGL_RENDERER), t.market.waitForData(CameraData)]),
      o = i.getCamera()
    this.raycaster = new Raycaster(o, funcUtil.traverseAncestorsVisible)
    t.addComponent(this, this.raycaster)
    this.defaultPointer = new RaycastPointerScreen(this.raycaster, n)
    this.snapcaster = new Snapcaster(n, this.raycaster)
    t.addComponent(this, this.snapcaster)
    const DataUpdate = new RaycastPointerDataUpdate(this, this.raycasterPointerData, n)
    t.addComponent(this, DataUpdate)
  }
  dispose() {}
  setupOctree(e) {
    this.raycaster.setupOctree(e)
    this.snapcaster.setupOctree(e)
  }
  getOctree() {
    return this.raycaster.getOctree()
  }
  get pointer() {
    return null !== this.overridePointer ? this.overridePointer : this.defaultPointer
  }
  get targets() {
    return this.raycaster
  }
  get picking() {
    return this.raycaster
  }
  get snapping() {
    return this.snapcaster
  }
}
